package com.gitee.loyo;

import com.gitee.loyo.controller.*;
import io.vertx.core.Handler;
import io.vertx.core.Vertx;
import io.vertx.core.buffer.Buffer;
import io.vertx.core.http.HttpMethod;
import io.vertx.core.http.HttpServerRequest;
import io.vertx.core.http.HttpServerResponse;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import javax.ws.rs.*;
import java.lang.annotation.Annotation;
import java.lang.reflect.Method;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.stream.Collectors;
import java.util.stream.Stream;

public class Pages {
    private final static Logger log = LoggerFactory.getLogger(Pages.class);
    private final static List<Route> router = new ArrayList<>();

    static {
        List<Object> controllers = Stream.of(
            new AssertsController(), new AuthController(),
            new BridgeController(),
            new JumpController(),
            new EndpointController(),
            new LogController()
        ).collect(Collectors.toList());
        for (Object controller : controllers) {
            for (Method method : controller.getClass().getMethods()) {
                HttpMethod httpMethod = null;
                String uri = null;
                for (Annotation annotation : method.getAnnotations()) {
                    if(annotation instanceof Path){
                        uri = ((Path)annotation).value();
                    }else if(annotation instanceof GET){
                        httpMethod = HttpMethod.GET;
                    }else if(annotation instanceof POST){
                        httpMethod = HttpMethod.POST;
                    }else if(annotation instanceof PATCH){
                        httpMethod = HttpMethod.PATCH;
                    }else if(annotation instanceof PUT){
                        httpMethod = HttpMethod.PUT;
                    }else if(annotation instanceof DELETE){
                        httpMethod = HttpMethod.DELETE;
                    }else if(annotation instanceof OPTIONS){
                        httpMethod = HttpMethod.OPTIONS;
                    }else if(annotation instanceof HEAD){
                        httpMethod = HttpMethod.HEAD;
                    }
                }
                if(httpMethod != null && uri != null){
                    String key = httpMethod.name() + " " + uri;
                    router.add(new Route(httpMethod, uri, req -> {
                        req.pause();
                        Vertx vertx = Vertx.currentContext().owner();
                        vertx.<Void>executeBlocking(p -> {
                            try {
                                try {
                                    method.invoke(controller, req, p);
                                }finally {
                                    req.resume();
                                }
                            } catch (Exception e) {
                                p.fail(e);
                            }
                        }, ar -> {
                            if(ar.failed()){
                                log.error(key + " error", ar.cause());
                                if(!req.response().ended()){
                                    req.response().setStatusCode(500).end();
                                }
                            }
                        });
                    }));
                }
            }
        }
    }

    public static void dispatch(HttpServerRequest request) {
        Iterator<Pages.Route> ite = router.iterator();
        while (ite.hasNext()){
            Pages.Route route = ite.next();
            if(route.matches(request)){
                route.handler.handle(request);
                return;
            }
        }
        request.response().setStatusCode(404).setStatusMessage("Not Found").end();
    }

    public static void renderPage(HttpServerResponse response, String page){
        Buffer buffer = Buffer.buffer(page.getBytes(StandardCharsets.UTF_8));
        response.setStatusCode(200)
            .putHeader("Content-Type", "text/html; charset=UTF-8")
            .putHeader("Content-Length", String.valueOf(buffer.length()))
            .end(buffer)
            .onComplete(ar -> {
                if(ar.failed()){
                    log.error("page render error", ar.cause());
                }
            });
    }

    private static class Route implements Comparable<Route>{
        final HttpMethod httpMethod;
        final String path;
        final Handler<HttpServerRequest> handler;
        final String string;

        public Route(HttpMethod httpMethod, String path, Handler<HttpServerRequest> handler){
            this.httpMethod = httpMethod;
            this.path = path;
            this.handler = handler;
            this.string = httpMethod.name() + " " + path;
        }

        public boolean matches(HttpServerRequest request) {
            if(!httpMethod.equals(request.method()))
                return false;

            String requestPath = request.path();
            if(path.equals(requestPath))
                return true;

            if(path.endsWith("*") && requestPath.regionMatches(0, path, 0, path.length() - 1)){
                return true;
            }

            return false;
        }

        @Override
        public String toString() {
            return string;
        }

        @Override
        public boolean equals(Object o) {
            if (this == o) return true;
            if (o == null || getClass() != o.getClass()) return false;

            Route route = (Route) o;
            return string.equals(route.string);
        }

        @Override
        public int hashCode() {
            return string.hashCode();
        }

        @Override
        public int compareTo(Route o) {
            String thisPath = path;
            if(thisPath.endsWith("/*")){
                thisPath = thisPath.substring(0, thisPath.length() - 2);
            }
            String targetPath = o.path;
            if(targetPath.endsWith("/*")){
                targetPath = targetPath.substring(0, targetPath.length() - 2);
            }
            return thisPath.compareTo(targetPath);
        }
    }
}
